//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Text;
using System.Reflection;
using Microsoft.Cci;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Mobile.ObjectBuilder;

namespace Microsoft.Practices.ObjectBuilder.ObGen
{
	[ConfigurationElementType(typeof(PolicyGeneratorFactoryElement<ConstructorPolicyGenerator>))]
	public class ConstructorPolicyGenerator : MethodBaseDependencyPolicyGenerator
	{
		public override CodeStatementCollection GenerateStatements(Type typeToProcess, CodeTypeDeclaration generatedType, NamingContext namingContext)
		{
			TypeNode metadataType = MetadataCache.GetTypeMetadata(typeToProcess);
			InstanceInitializer ctor = SelectConstructor(metadataType);

			if (typeToProcess.IsInterface || typeToProcess.IsAbstract || ctor == null || !ctor.IsPublic)
				return new CodeStatementCollection();

			base.AddAssemblyReference(typeToProcess.Assembly);
			base.AddNamespaceImport(typeof(ConstructorInvokeHandler).Namespace);
			base.AddNamespaceImport(typeof(MethodParametersHandler).Namespace);

			// Generate an inner type that implements the methods required for the strong typed policy. 
			// This allows the code to render and compile with VB too.

			// private class TypedConstructorPolicy : StrongTypedCreationPolicy
			CodeTypeDeclaration policy = new CodeTypeDeclaration(namingContext.GetMemberName("TypedConstructorPolicy"));
			policy.BaseTypes.Add(typeof(StrongTypedCreationPolicy));

			// private static object CreateInstance(object[] parameters)
			policy.Members.Add(GenerateCreateInstanceMethod(typeToProcess, ctor));

			// private static object[] GetParameters(IBuilderContext context, string id)
			policy.Members.Add(GenerateGetParametersMethod("GetParameters", ctor.Parameters));

			//public ConstructorPolicy()
			//    : base(new ConstructorInvokeHandler(CreateInstance), 
			//			 new MethodParametersHandler(GetParameters),
			//			 typeof(IService), typeof(IFoo), typeof(IBar))
			//{
			//}
			CodeConstructor codeCtor = new CodeConstructor();
			codeCtor.Attributes = MemberAttributes.Public;
			codeCtor.BaseConstructorArgs.Add(
				new CodeDelegateCreateExpression(
					new CodeTypeReference(typeof(ConstructorInvokeHandler).Name),
					new CodeTypeReferenceExpression(policy.Name),
					"CreateInstance"));
			codeCtor.BaseConstructorArgs.Add(
				new CodeDelegateCreateExpression(
					new CodeTypeReference(typeof(MethodParametersHandler).Name),
					new CodeTypeReferenceExpression(policy.Name),
					"GetParameters"));
			foreach (ParameterInfo parameter in ctor.GetConstructorInfo().GetParameters())
			{
				codeCtor.BaseConstructorArgs.Add(
					new CodeTypeOfExpression(parameter.ParameterType));
				base.AddAssemblyReference(parameter.ParameterType.Assembly);
			}

			policy.Members.Add(codeCtor);
			generatedType.Members.Add(policy);

			CodeStatementCollection statements = new CodeStatementCollection();

			// Only register the policy if the ctor policy is not being overriden with a 
			// transient policy other than the default.
			// ICreationPolicy existingCreationPolicy = existingPolicies.Get<ICreationPolicy>(typeToBuild, idToBuild);
			CodeVariableDeclarationStatement existingCreationPolicy = new CodeVariableDeclarationStatement(
				typeof(ICreationPolicy).Name, namingContext.GetVariableName("existingCreationPolicy"),
				new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(
						new CodeArgumentReferenceExpression("existingPolicies"),
						"Get",
						new CodeTypeReference(typeof(ICreationPolicy).Name)
					),
					new CodeArgumentReferenceExpression("typeToBuild"),
					new CodeArgumentReferenceExpression("idToBuild")
				)
			);
			statements.Add(existingCreationPolicy);

			// if (existingCreationPolicy != null || existingCreationPolicy.GetType() == typeof(DefaultCreationPolicy))
			statements.Add(new CodeConditionStatement(
				new CodeBinaryOperatorExpression(
					new CodeBinaryOperatorExpression(
						new CodeVariableReferenceExpression(existingCreationPolicy.Name),
						CodeBinaryOperatorType.ValueEquality,
						new CodePrimitiveExpression(null)
					),
					CodeBinaryOperatorType.BooleanOr,
					new CodeBinaryOperatorExpression(
						new CodeMethodInvokeExpression(
							new CodeVariableReferenceExpression(existingCreationPolicy.Name),
							"GetType"
						),
						CodeBinaryOperatorType.ValueEquality,
						new CodeTypeOfExpression(typeof(DefaultCreationPolicy).Name)
					)
				),
				/* policies.Set<ICreationPolicy>(new TypedConstructorPolicy(), typeToBuild, idToBuild); */
				base.SetPolicyStatement(typeof(ICreationPolicy), new CodeObjectCreateExpression(policy.Name))
			));

			return statements;
		}

		private static CodeMemberMethod GenerateCreateInstanceMethod(Type typeToProcess, InstanceInitializer ctor)
		{
			// object[]
			CodeTypeReference objectArray = new CodeTypeReference(new CodeTypeReference(typeof(object)), 1);

			// private static object CreateInstance(object[] parameters)
			CodeMemberMethod createMethod = new CodeMemberMethod();
			createMethod.Name = "CreateInstance";
			createMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			createMethod.ReturnType = new SafeCodeTypeReference(typeof(object));
			createMethod.Parameters.Add(new CodeParameterDeclarationExpression(objectArray, "parameters"));

			//    return new TypeToBuild(
			//        (IService)parameters[0],
			//        (IFoo)parameters[1],
			//        (IBar)parameters[2]);
			CodeObjectCreateExpression createObject = new CodeObjectCreateExpression(typeToProcess);
			ParameterInfo[] parameters = ctor.GetConstructorInfo().GetParameters();
			for (int i = 0; i < parameters.Length; i++)
			{
				ParameterInfo parameter = parameters[i];
				createObject.Parameters.Add(
					// (IFoo)
					new CodeCastExpression(parameter.ParameterType,
					// parameters[i]
						new CodeArrayIndexerExpression(
							new CodeArgumentReferenceExpression("parameters"),
							new CodePrimitiveExpression(i))));
			}

			createMethod.Statements.Add(new CodeMethodReturnStatement(createObject));

			return createMethod;
		}

		/// <summary>
		/// Default implementation that selects the first constructor in the type, or 
		/// <see langword="null" /> if the class provides none.
		/// </summary>
		protected virtual InstanceInitializer SelectConstructor(TypeNode metadataType)
		{
			MemberList constructors = metadataType.GetConstructors();

			if (constructors.Length == 1) return (InstanceInitializer)constructors[0];

			if (constructors.Length > 0)
			{
				InstanceInitializer injectionCtor = null;
				foreach (InstanceInitializer constructor in constructors)
				{
					if (constructor.IsPublic && Attribute.IsDefined(constructor.GetConstructorInfo(), typeof(InjectionConstructorAttribute)))
					{
						// Multiple decorated constructors aren't valid
						if (injectionCtor != null)
						{
							throw new InvalidOperationException(String.Format(
								CultureInfo.CurrentCulture,
								Properties.Resources.DuplicateConstructorInjectionAttribute,
								metadataType.FullName));
						}
						injectionCtor = constructor;
					}
				}

				if (injectionCtor == null)
				{
					injectionCtor = (InstanceInitializer)constructors[0];
				}

				return injectionCtor;
			}
			else
			{
				return null;
			}
		}
	}
}
